﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using ArgusLib.Threading;

namespace ArgusLib.Controls
{
	public partial class ProgressBox : ArgusLib.Controls.BaseForm
	{
		public delegate void SetProgressTextHandler(string text);

		Timer timer;
		DateTime startTime;

		public ProgressBox()
			: this(false, false, false) { }

		public ProgressBox(bool canAbort, bool canPause, bool canShowDetails)
		{
			InitializeComponent();
			this.lProgress.Text = (0.0).ToString("p");
			this.ShowProcessTasksProgress = false;
			this.CanShowDetails = canShowDetails;
			this.CanAbort = canAbort;
			this.CanPause = canPause;
			this.timer = new Timer();
			this.timer.Interval = 200;
			this.timer.Tick += timer_Tick;
			this.AutoClose = true;
		}

		void timer_Tick(object sender, EventArgs e)
		{
			TimeSpan span = DateTime.Now - startTime;
			this.lElapsedTime.StatusText = string.Format("{0:d2}:{1:d2}:{2:d2}", span.Hours, span.Minutes, span.Seconds);
		}

		protected override void OnShown(EventArgs e)
		{
			base.OnShown(e);
			this.startTime = DateTime.Now;
			this.timer.Start();
		}

		private void flpBorder_ClientSizeChanged(object sender, EventArgs e)
		{
			this.progressBar1.Width = this.flpBorder.ClientSize.Width - this.progressBar1.Margin.Horizontal;
			this.outputBox1.Width = this.flpBorder.ClientSize.Width - this.outputBox1.Margin.Horizontal;
			this.pButtons.Width = this.flpBorder.ClientSize.Width - this.pButtons.Margin.Horizontal;
		}

		public bool AutoClose { get; set; }
		public bool CanAbort { get { return this.bAbort.Visible; } private set { this.bAbort.Visible = value; } }
		public bool CanPause { get { return (this.bPause.Visible || this.bResume.Visible); } private set { this.bPause.Visible = value; } }
		public bool CanShowDetails { get { return this.bShowDetails.Visible; } private set { this.bShowDetails.Visible = value; } }
		protected bool ShowProcessTasksProgress { get { return this.flpProcessTasksProgress.Visible; } set { this.flpProcessTasksProgress.Visible = value; } }

		public event EventHandler CancelRequested;
		public event EventHandler PauseRequested;
		public event EventHandler ResumptionRequested;

		public void SetProgress(double progress)
		{
			if (ArgusLib.Environment.Runtime == ArgusLib.Runtime.Mono)
			{
				if (this.IsShown == false)
					return;
			}

			if (this.InvokeRequired == true)
				this.Invoke(new Action<double>(this.SetProgressInvoke), progress);
			else
				this.SetProgressInvoke(progress);
		}

		private void SetProgressInvoke(double progress)
		{
			this.progressBar1.Value = this.progressBar1.Minimum + (int)(progress * (this.progressBar1.Maximum - this.progressBar1.Minimum));
			this.lProgress.Text = progress.ToString("p");
		}

		public void SetProgressProcessedTasks(int processed, int remaining)
		{
			if (ArgusLib.Environment.Runtime == ArgusLib.Runtime.Mono)
			{
				if (this.IsShown == false)
					return;
			}

			if (this.InvokeRequired == true)
				this.Invoke(new Action<int, int>(this.SetProgressProcessedTasksInvoke), processed, remaining);
			else
				this.SetProgressProcessedTasksInvoke(processed, remaining);
		}

		private void SetProgressProcessedTasksInvoke(int processed, int remaining)
		{
			this.lProcessedTasks.StatusText = processed.ToString("n0");
			this.lRemainingTasks.StatusText = remaining.ToString("n0");
		}

		public void OutputDetail(string text)
		{
			this.outputBox1.InvokeOutput(text);
		}

		private void bAbort_Click(object sender, EventArgs e)
		{
			this.OnCancelRequested(EventArgs.Empty);
		}

		protected virtual void OnCancelRequested(EventArgs e)
		{
			if (this.CancelRequested != null)
				this.CancelRequested(this, e);
		}

		protected virtual void OnPauseRequested(EventArgs e)
		{
			if (this.PauseRequested != null)
				this.PauseRequested(this, e);
		}

		protected virtual void OnResumptionRequested(EventArgs e)
		{
			if (this.ResumptionRequested != null)
				this.ResumptionRequested(this, e);
		}

		/// <summary>
		/// Executes <see cref="BackgroundWorker.RunWorkerAsync"/> and shows the progress in a modal
		/// <see cref="ProgressBox"/>. The ProgressBox is closed when the <see cref="BackgroundWorker.RunWorkerCompleted"/>-Event
		/// is raised.
		/// </summary>
		/// <param name="backgroundWorker"></param>
		/// <param name="argument"></param>
		public static DialogResult Show(BackgroundWorker backgroundWorker, object argument)
		{
			return ProgressBox.Show(backgroundWorker, argument, true);
		}

		/// <summary>
		/// Executes <see cref="BackgroundWorker.RunWorkerAsync"/> and shows the progress in a modal
		/// <see cref="ProgressBox"/>. The ProgressBox is closed when the <see cref="BackgroundWorker.RunWorkerCompleted"/>-Event
		/// is raised.
		/// </summary>
		/// <param name="backgroundWorker"></param>
		/// <param name="argument"></param>
		public static DialogResult Show(BackgroundWorker backgroundWorker, object argument, bool autoClose)
		{
			ProgressBox progressBox = new ProgressBox(backgroundWorker.WorkerSupportsCancellation, false, false);
			progressBox.AutoClose = autoClose;
			DialogResult RetVal = DialogResult.OK;
			progressBox.CancelRequested += new EventHandler((e1, e2) =>
				{
					backgroundWorker.CancelAsync();
				});
			backgroundWorker.ProgressChanged += (sender, e) =>
				{
					progressBox.SetProgress(e.ProgressPercentage / 100.0);
					if (e.UserState != null)
						progressBox.OutputDetail(e.UserState.ToString());
				};
			backgroundWorker.RunWorkerCompleted += (sender, e) =>
				{
					if (e.Cancelled == true)
					{
						RetVal = DialogResult.Cancel;
						if (progressBox.IsShown == true)
							progressBox.Close();
					}
					else if (progressBox.AutoClose == true)
					{
						if (progressBox.IsShown == true)
							progressBox.Close();
					}
					else
					{
						progressBox.CanAbort = false;
						progressBox.CanPause = false;
						progressBox.bClose.Visible = true;
					}
				};
			backgroundWorker.RunWorkerAsync(argument);
			progressBox.ShowDialog();
			return RetVal;
		}

		public static DialogResult Show(BackgroundWorker backgroundWorker, bool autoClose)
		{
			return ProgressBox.Show(backgroundWorker, null, autoClose);
		}

		public static DialogResult Show(BackgroundWorker backgroundWorker)
		{
			return ProgressBox.Show(backgroundWorker, null, true);
		}

		[RequiresAssembly(typeof(Parallelization))]
		public static DialogResult ShowForEach<T>(IEnumerable collection, Parallelization.ForEachBody<T> body)
		{
			return ProgressBox.ShowForEach<T>(collection, body, true);
		}

		[RequiresAssembly(typeof(Parallelization))]
		public static DialogResult ShowForEach<T>(IEnumerable collection, Parallelization.ForEachBody<T> body, bool autoClose)
		{
			ProgressBox progressBox = new ProgressBox(true, true, false);
			progressBox.AutoClose = autoClose;
			ThreadState threadState = new ThreadState();
			threadState.OutputReceived += new Action<string>((text) =>
				{
					if (progressBox.CanShowDetails == false)
						progressBox.Invoke(new Action(() => { progressBox.CanShowDetails = true; }));
					progressBox.OutputDetail(text);
				});
			DialogResult RetVal = DialogResult.OK;
			progressBox.CancelRequested += new EventHandler((e1, e2) =>
				{
					RetVal = DialogResult.Cancel;
					threadState.RequestCancel();
				});
			progressBox.PauseRequested += new EventHandler((e1, e2) =>
				{
					threadState.Suspend();
				});
			progressBox.ResumptionRequested += new EventHandler((e1, e2) =>
				{
					threadState.Resume();
				});
			System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback((arg)=>
				{
					Parallelization.ProgressChangedHandler progressChanged = new Parallelization.ProgressChangedHandler(progressBox.SetProgress);
					Parallelization.ForEach<T>(collection, body, threadState, progressChanged);
					progressBox.Invoke(new Action(() =>
						{
							if (threadState.CancelRequested == true)
							{
								if (progressBox.IsShown == true)
									progressBox.Close();
							}
							else if (progressBox.AutoClose == true)
							{
								if (progressBox.IsShown == true)
									progressBox.Close();
							}
							else
							{
								progressBox.CanAbort = false;
								progressBox.CanPause = false;
								progressBox.bClose.Visible = true;
							}
						}));
				}));
			progressBox.ShowDialog();
			return RetVal;
		}

		[RequiresAssembly(typeof(Parallelization))]
		public static DialogResult ShowFor(int count, Parallelization.ForBody body)
		{
			return ProgressBox.ShowFor(count, body, true);
		}

		[RequiresAssembly(typeof(Parallelization))]
		public static DialogResult ShowFor(int count, Parallelization.ForBody body, bool autoClose)
		{
			ProgressBox progressBox = new ProgressBox(true, true, false);
			progressBox.AutoClose = autoClose;
			ThreadState threadState = new ThreadState();
			threadState.OutputReceived += new Action<string>((text) =>
			{
				if (progressBox.CanShowDetails == false)
					progressBox.Invoke(new Action(() => { progressBox.CanShowDetails = true; }));
				progressBox.OutputDetail(text);
			});
			DialogResult RetVal = DialogResult.OK;
			progressBox.CancelRequested += new EventHandler((e1, e2) =>
			{
				RetVal = DialogResult.Cancel;
				threadState.RequestCancel();
			});
			progressBox.PauseRequested += new EventHandler((e1, e2) =>
			{
				threadState.Suspend();
			});
			progressBox.ResumptionRequested += new EventHandler((e1, e2) =>
			{
				threadState.Resume();
			});
			System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback((arg) =>
			{
				Parallelization.ProgressChangedHandler progressChanged = new Parallelization.ProgressChangedHandler(progressBox.SetProgress);
				Parallelization.For(count, body, threadState, progressChanged);
				progressBox.Invoke(new Action(() =>
				{
					if (threadState.CancelRequested == true)
					{
						if (progressBox.IsShown == true)
							progressBox.Close();
					}
					else if (progressBox.AutoClose == true)
					{
						if (progressBox.IsShown == true)
							progressBox.Close();
					}
					else
					{
						progressBox.CanAbort = false;
						progressBox.CanPause = false;
						progressBox.bClose.Visible = true;
					}
				}));
			}));
			progressBox.ShowDialog();
			return RetVal;
		}

		public static DialogResult ShowFor(int count, Action<int> body)
		{
			return ProgressBox.ShowFor(count, body, true);
		}

		public static DialogResult ShowFor(int count, Action<int> body, bool autoClose)
		{
			ProgressBox progressBox = new ProgressBox();
			progressBox.AutoClose = autoClose;
			System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback((arg) =>
			{
				for (int i = 0; i < count; i++)
				{
					body(i);
					progressBox.SetProgress((double)(i + 1) / count);
				}
				progressBox.Invoke(new Action(() =>
				{
					if (progressBox.AutoClose == true)
					{
						if (progressBox.IsShown == true)
							progressBox.Close();
					}
					else
					{
						progressBox.CanAbort = false;
						progressBox.CanPause = false;
						progressBox.bClose.Visible = true;
					}
				}));
			}));
			progressBox.ShowDialog();
			return DialogResult.OK;
		}

		[RequiresAssembly(typeof(Parallelization))]
		public static DialogResult ShowProcessTasks(IEnumerable<Parallelization.TaskBody> tasks)
		{
			return ProgressBox.ShowProcessTasks(tasks, true);
		}

		[RequiresAssembly(typeof(Parallelization))]
		public static DialogResult ShowProcessTasks(IEnumerable<Parallelization.TaskBody> tasks, bool autoClose)
		{
			return ProgressBox.ShowProcessTasks(tasks, System.Environment.ProcessorCount, autoClose);
		}

		[RequiresAssembly(typeof(Parallelization))]
		public static DialogResult ShowProcessTasks(IEnumerable<Parallelization.TaskBody> tasks, int numberOfThreads, bool autoClose)
		{
			ProgressBox progressBox = new ProgressBox(true, true, false);
			progressBox.AutoClose = autoClose;
			progressBox.ShowProcessTasksProgress = true;
			DialogResult RetVal = DialogResult.OK;
			ThreadState threadState = new ThreadState();
			threadState.OutputReceived += new Action<string>((text) =>
			{
				if (progressBox.CanShowDetails == false)
					progressBox.Invoke(new Action(() => { progressBox.CanShowDetails = true; }));
				progressBox.OutputDetail(text);
			});
			progressBox.CancelRequested += new EventHandler((e1, e2) =>
			{
				RetVal = DialogResult.Cancel;
				threadState.RequestCancel();
			});
			progressBox.PauseRequested += new EventHandler((e1, e2) =>
			{
				threadState.Suspend();
			});
			progressBox.ResumptionRequested += new EventHandler((e1, e2) =>
			{
				threadState.Resume();
			});

			System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback((arg) =>
				{
					Parallelization.ProcessTasksProgressHandler progressChanged = new Parallelization.ProcessTasksProgressHandler((noProcessed, noRemainig) =>
						{
							progressBox.SetProgressProcessedTasks(noProcessed, noRemainig);
						});
					Parallelization.ProcessTasks(tasks, numberOfThreads, threadState, progressChanged);
					progressBox.Invoke(new Action(() =>
						{
							if (threadState.CancelRequested == true)
							{
								if (progressBox.IsShown == true)
									progressBox.Close();
							}
							else if (progressBox.AutoClose == true)
							{
								if (progressBox.IsShown == true)
									progressBox.Close();
							}
							else
							{
								progressBox.CanAbort = false;
								progressBox.CanPause = false;
								progressBox.bClose.Visible = true;
							}
						}));
				}));
			progressBox.ShowDialog();
			return RetVal;
		}

		private void bPause_Click(object sender, EventArgs e)
		{
			this.bPause.Visible = false;
			this.bResume.Visible = true;
			this.OnPauseRequested(EventArgs.Empty);
		}

		private void bResume_Click(object sender, EventArgs e)
		{
			this.bResume.Visible = false;
			this.bPause.Visible = true;
			this.OnResumptionRequested(EventArgs.Empty);
		}

		private void bShowDetails_Click(object sender, EventArgs e)
		{
			this.outputBox1.Visible = this.bShowDetails.Pressed;
		}

		private void bClose_Click(object sender, EventArgs e)
		{
			this.Close();
		}

		public class ThreadState : ArgusLib.Threading.ThreadState
		{
			internal event Action<string> OutputReceived;

			public void SendOutput(string text)
			{
				if (this.OutputReceived != null)
					this.OutputReceived(text);
			}
		}

		public static void ShowMarquee(string text, Action action)
		{
			Action<SetProgressTextHandler> newAction = new Action<SetProgressTextHandler>((arg) => { action(); });
			ProgressBox.ShowMarquee(text, newAction);
		}

		public static void ShowMarquee(string text, Action<SetProgressTextHandler> action)
		{
			if (action == null)
				throw new ArgumentNullException("action");
			Private.MarqueeProgressBox pb = new Private.MarqueeProgressBox();
			pb.ProgressText = text;
			SetProgressTextHandler RetVal = new SetProgressTextHandler((newText) => { pb.ProgressText = newText; });
			System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback((arg) =>
				{
					action(RetVal);
					pb.Invoke(new Action(() =>
						{
							if (pb.IsShown == true)
								pb.Close();
						}));
				}));
			pb.ShowDialog();
		}
	}
}
